package com.flyme.common.utils.thrift;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;

public class ThriftBeanUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ThriftBeanUtil.class);

    private static LoadingCache<String, Map<String, Field>> fieldCacheMap = CacheBuilder.from("maximumSize=200,expireAfterAccess=1d").
        build(new CacheLoader<String, Map<String, Field>>() {
            @Override
            public Map<String, Field> load(String key) throws Exception {
                Class aClass = Class.forName(key);
                Field[] declaredFields = aClass.getDeclaredFields();
                Map<String, Field> fieldMap = Maps.newConcurrentMap();
                for (Field field : declaredFields) {
                    fieldMap.put(field.getName(), field);
                    field.setAccessible(true);
                }
                return fieldMap;
            }
        });

    /**
     * 对象转化为map
     *
     * @param obj
     * @return
     * @throws Exception
     */
    public static Map<String, Object> objectToMap(Object obj) throws Exception {
        Map<String, Object> map = Maps.newHashMap();

        if (obj == null) return map;

        Object value;
        for (Field field : obj.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            if (null != (value = field.get(obj)))
                map.put(field.getName(), value);
        }
        return map;
    }


    /**
     * 以src的字段为依据向thrift对象设置字段值，调用thrift对象的set方法
     *
     * @param thrift
     * @param src
     */
    public static void copyPropertiesToThrift(Object thrift, Object src) {
        Class<?> srcClass = src.getClass();
        Class<?> thriftClass = thrift.getClass();

        Map<String, Field> srcFields = fieldCacheMap.getUnchecked(srcClass.getCanonicalName());
        Map<String, Field> thriftFields = fieldCacheMap.getUnchecked(thriftClass.getCanonicalName());

        for (Field srcField : srcFields.values()) {
            try {
                Field thriftField = thriftFields.get(srcField.getName());
                if (thriftField != null &&
                    (thriftField.getType().equals(srcField.getType())
                        || thriftField.getType().isPrimitive() && srcField.getType().getSuperclass().getTypeName().equals(Number.class.getTypeName())
                        || srcField.getType().isPrimitive() && thriftField.getType().getSuperclass().getTypeName().equals(Number.class.getTypeName()))) {
                    Object o = srcField.get(src);
                    if (o != null) {//必须调用set方法设置值，否则optional申明的字段将不回序列化
                        Method method = thriftClass.getDeclaredMethod("set" + thriftField.getName().substring(0, 1).toUpperCase() + thriftField.getName().substring(1), thriftField.getType());
                        method.invoke(thrift, o);
                    }
                }
            } catch (Exception e) {
                LOGGER.warn("Copy property error", e);
            }

        }
    }

    /**
     * 以dest的字段为依据从thrift对象获取值
     *
     * @param dest
     * @param thrift
     */
    public static void copyPropertiesFromThrift(Object dest, Object thrift) {
        Class<?> thriftClass = thrift.getClass();
        Class<?> destClass = dest.getClass();

        Map<String, Field> thriftFields = fieldCacheMap.getUnchecked(thriftClass.getCanonicalName());
        Map<String, Field> destFields = fieldCacheMap.getUnchecked(destClass.getCanonicalName());

        for (Field destField : destFields.values()) {
            try {
                Field thriftField = thriftFields.get(destField.getName());
                if (thriftField != null &&
                    (thriftField.getType().equals(destField.getType())
                        || thriftField.getType().isPrimitive() && destField.getType().getSuperclass().getTypeName().equals(Number.class.getTypeName())
                        || destField.getType().isPrimitive() && thriftField.getType().getSuperclass().getTypeName().equals(Number.class.getTypeName()))) {
                    Object o = thriftField.get(thrift);
                    if (o != null) {
                        destField.set(dest, o);
                    }
                }
            } catch (Exception e) {
                LOGGER.warn("Copy property error", e);
            }

        }
    }

    /**
     * 以check的字段为依据从thrift对象获取值
     *
     * @param dest
     * @param thrift
     */
    public static void copyPropertiesFromThrift(Object dest, Object thrift, Object check) {
        Class<?> thriftClass = thrift.getClass();
        Class<?> destClass = dest.getClass();
        Class<?> checkClass = check.getClass();

        Map<String, Field> thriftFields = fieldCacheMap.getUnchecked(thriftClass.getCanonicalName());
        Map<String, Field> destFields = fieldCacheMap.getUnchecked(destClass.getCanonicalName());
        Map<String, Field> checkFields = fieldCacheMap.getUnchecked(checkClass.getCanonicalName());

        for (Field destField : destFields.values()) {

            try {
                Field checkField = checkFields.get(destField.getName());
                Object checkValue = checkField.get(check);
                if (checkValue != null && (boolean) checkValue) {
                    Field thriftField = thriftFields.get(destField.getName());
                    if (thriftField != null &&
                        (thriftField.getType().equals(destField.getType())
                            || thriftField.getType().isPrimitive() && destField.getType().getSuperclass().getTypeName().equals(Number.class.getTypeName())
                            || destField.getType().isPrimitive() && thriftField.getType().getSuperclass().getTypeName().equals(Number.class.getTypeName()))) {
                        Object o = thriftField.get(thrift);
                        if (o != null) {
                            destField.set(dest, o);
                        }
                    }
                }
            } catch (Exception e) {
                LOGGER.warn("Copy property error", e);
            }

        }
    }
}
